using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using TTXNA.src.Util;
using TTXNA.src.Engine.Terrain;
using Processors;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace TTXNA.src.Engine.Graphics
{
    public class TTTerrain : TTObject
    {
        private Effect effect;
        private EffectParameter wvpParameter;
        private EffectParameter textureParameter;

        private VertexDeclaration vertexDeclaration;

        private Texture2D grassTex;

        public Rectangle viewableLand;
        private const int border = 10;

        private TerrainVertex[,][] vertices;

        private VertexBuffer[,] vertexBuffers;
        private bool[,] updateNeeded;

        private IndexBuffer[,] gridIndexBuffer;
        private int[] sectorIndices;
        
        private const int splitSize = 16;

        private static Dictionary<int, Vector2[]> texCoords = new Dictionary<int, Vector2[]>();
        private Map map;

        public readonly VertexElement[] TerrainVertexDeclaration = new VertexElement[]
		{
            new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
            new VertexElement(0, 12, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0),
            new VertexElement(0, 24, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0)
	    };

        static TTTerrain()
        {
            texCoords.Add(0, new Vector2[] { new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1), new Vector2(0, 0) });
            texCoords.Add(1, new Vector2[] { new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1) });
            texCoords.Add(2, new Vector2[] { new Vector2(0, 1), new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1) });
            texCoords.Add(3, new Vector2[] { new Vector2(1, 1), new Vector2(0, 1), new Vector2(0, 0), new Vector2(1, 0) });
        }

        public TTTerrain(Map map) : base(ObjectType.Terrain)
        {
            this.map = map;

            if (map.Width % splitSize != 0 || map.Length % splitSize != 0)
            {
                throw new Exception("ZOMGZ mutliple of splitsize");
            }

            vertexDeclaration = new VertexDeclaration(TTXNAGame.graphics.GraphicsDevice, TerrainVertexDeclaration);
            
            SetupEffect();

            viewableLand = Rectangle.Empty;

            grassTex = TTXNAGame.content.Load<Texture2D>("Assets/Textures/Ground/grass");

            BuildGrid();
        }

        private void SetupEffect()
        {
            effect = TTXNAGame.content.Load<Effect>("Assets/Shaders/TerrainShader");

            wvpParameter = effect.Parameters["WorldViewProjection"];
            textureParameter = effect.Parameters["tex"];
        }

        private void BuildGrid()
        {
            BuildFourTriangleVB();
            BuildIndices();
        }

        private void BuildFourTriangleVB()
        {
            vertices = new TerrainVertex[map.Width / splitSize, map.Length / splitSize][];

            int vertsPerSector = splitSize * splitSize * 12; ;

            Vector3 normal = new Vector3(0, 1, -1);
            normal.Normalize();

            for (int zs = 0; zs < map.Length / splitSize; zs++)
            {
                int zss = zs * splitSize;

                for (int xs = 0; xs < map.Width / splitSize; xs++)
                {
                    int xss = xs * splitSize;

                    vertices[xs, zs] = new TerrainVertex[vertsPerSector];
                    int num = 0;

                    for (int z = 0; z < splitSize; z++)
                    {
                        for (int x = 0; x < splitSize; x++)
                        {
                            TerrainVertex neVertex = new TerrainVertex(new Vector3(x + 0.5f + xss, 0, z - 0.5f + zss), Vector3.Up, texCoords[0][0]);
                            TerrainVertex seVertex = new TerrainVertex(new Vector3(x + 0.5f + xss, 0, z + 0.5f + zss), Vector3.Up, texCoords[0][1]);
                            TerrainVertex swVertex = new TerrainVertex(new Vector3(x - 0.5f + xss, 0, z + 0.5f + zss), Vector3.Up, texCoords[0][2]);
                            TerrainVertex nwVertex = new TerrainVertex(new Vector3(x - 0.5f + xss, 0, z - 0.5f + zss), Vector3.Up, texCoords[0][3]);
                            
                            //Centre
                            TerrainVertex cVertex = new TerrainVertex(new Vector3(x + xss, 0, z + zss), Vector3.Up, new Vector2(0.5f, 0.5f));
                            
                            //NorthTriangle
                            vertices[xs, zs][num++] = nwVertex; vertices[xs, zs][num++] = cVertex; vertices[xs, zs][num++] = neVertex;
                            //EastTriangle
                            vertices[xs, zs][num++] = neVertex; vertices[xs, zs][num++] = cVertex; vertices[xs, zs][num++] = seVertex;
                            //SouthTriangle
                            vertices[xs, zs][num++] = seVertex; vertices[xs, zs][num++] = cVertex; vertices[xs, zs][num++] = swVertex;
                            //WestTriangle
                            vertices[xs, zs][num++] = swVertex; vertices[xs, zs][num++] = cVertex; vertices[xs, zs][num++] = nwVertex;
                        }
                    }
                }
            }

            BuildVertexBuffer();
        }

        private void BuildIndices()
        {
            CreateIndices();
        }

        public void FinaliseIndexBuffers()
        {
            GraphicsDevice device = TTXNAGame.graphics.GraphicsDevice;

            gridIndexBuffer = new IndexBuffer[map.Width / splitSize, map.Length / splitSize];

            for (int z = 0; z < map.Length / splitSize; z++)
            {
                for (int x = 0; x < map.Width / splitSize; x++)
                {
                    gridIndexBuffer[x, z] = new IndexBuffer(device, typeof(int), splitSize * splitSize * 12, ResourceUsage.None);
                    gridIndexBuffer[x, z].SetData<int>(sectorIndices);
                }
            }           
        }

        public void CreateIndices()
        {
            sectorIndices = new int[splitSize * splitSize * 12];

            int index = 0;

            for (int z = 0; z < splitSize; z++)
            {
                for (int x = 0; x < splitSize; x++)
                {
                    int tileNum = z * splitSize + x;
                    int ttt = tileNum * 12;

                    sectorIndices[index++] = ttt;
                    sectorIndices[index++] = ttt + 1;
                    sectorIndices[index++] = ttt + 2;

                    sectorIndices[index++] = ttt + 3;
                    sectorIndices[index++] = ttt + 4;
                    sectorIndices[index++] = ttt + 5;

                    sectorIndices[index++] = ttt + 6;
                    sectorIndices[index++] = ttt + 7;
                    sectorIndices[index++] = ttt + 8;

                    sectorIndices[index++] = ttt + 9;
                    sectorIndices[index++] = ttt + 10;
                    sectorIndices[index++] = ttt + 11;
                }
            }

            FinaliseIndexBuffers();
        }

        private Coordinate GetCoordFor(float x, float z)
        {
            Coordinate tile = new Coordinate(-1, -1);

            if (x > -0.5 && z > -0.5 && x < map.Width - 0.5 && z < map.Length - 0.5)
            {
                tile.X = (int)Math.Floor(x + 0.5f);
                tile.Z = (int)Math.Floor(z + 0.5f);
            }

            return tile;
        }

        #region Picking
        public GridPickData Pick(Ray ray)
        {
            Vector3 zipMan = ray.Position;

            float fraction = 4;

            bool wentIntoGrid = false;
            bool leftGrid = false;

            Coordinate cameraTile = GetCoordFor(ray.Position.X, ray.Position.Z);

            if (cameraTile.X != -1) { wentIntoGrid = true; }
            Vector3 localDirection = ray.Direction;

            Dictionary<Coordinate, bool> possibles = new Dictionary<Coordinate, bool>();

            while (!(wentIntoGrid && leftGrid) && zipMan.Y > (Math.Abs(localDirection.Y) * -1) && zipMan.Y < 2000)
            {
                localDirection *= 1.0f / fraction;
                zipMan += localDirection;

                Coordinate overTile = GetCoordFor(zipMan.X, zipMan.Z);

                if (overTile.X != -1)
                {
                    if (zipMan.Y < (MaxHeight(overTile) + Math.Abs(localDirection.Y)) && zipMan.Y > (MinHeight(overTile) - Math.Abs(localDirection.Y)))
                    {

                        List<Coordinate> neighbours = Coordinate.GetNeighbours(overTile);

                        foreach (Coordinate c in neighbours)
                            possibles[c] = true;

                        possibles[overTile] = true;
                    }

                    if (!wentIntoGrid) { wentIntoGrid = true; }
                }
                else
                {
                    if (wentIntoGrid) { leftGrid = true; }
                }

                localDirection *= fraction;
            }

            int ne, se, sw, nw, cen;
            Vector3 vectorSE, vectorSW, vectorNW, vectorNE, vectorCentre;

            Coordinate nearestTile = new Coordinate(-1, -1);
            RotateType rotType = RotateType.Invalid;

            
            int xs = -1;
            int zs = -1;

            float dist = float.MaxValue;

            foreach (Coordinate c in possibles.Keys)
            {
                xs = c.X / splitSize;
                zs = c.Z / splitSize;

                int x = c.X % splitSize;
                int z = c.Z % splitSize;

                int tileNum = (x + (z * splitSize)) * 12;

                nw = tileNum;
                cen = tileNum + 1;
                ne = tileNum + 3;
                se = tileNum + 6;
                sw = tileNum + 9;
               
                vectorSE = new Vector3(vertices[xs, zs][se].X, vertices[xs, zs][se].Y, vertices[xs, zs][se].Z);
                vectorSW = new Vector3(vertices[xs, zs][sw].X, vertices[xs, zs][sw].Y, vertices[xs, zs][sw].Z);
                vectorNW = new Vector3(vertices[xs, zs][nw].X, vertices[xs, zs][nw].Y, vertices[xs, zs][nw].Z);
                vectorNE = new Vector3(vertices[xs, zs][ne].X, vertices[xs, zs][ne].Y, vertices[xs, zs][ne].Z);
                vectorCentre = new Vector3(vertices[xs, zs][cen].X, vertices[xs, zs][cen].Y, vertices[xs, zs][cen].Z);

                IntersectInformation intInfN;
                IntersectInformation intInfE;
                IntersectInformation intInfS;
                IntersectInformation intInfW;

                float? distN, distE, distS, distW;
                
                Intersection.RayIntersectsTriangle(ref ray, ref vectorNW, ref vectorCentre, ref vectorNE, out distN);
                Intersection.RayIntersectsTriangle(ref ray, ref vectorNE, ref vectorCentre, ref vectorSE, out distE);
                Intersection.RayIntersectsTriangle(ref ray, ref vectorSE, ref vectorCentre, ref vectorSW, out distS);
                Intersection.RayIntersectsTriangle(ref ray, ref vectorSW, ref vectorCentre, ref vectorNW, out distW);

                if (distN < dist) { nearestTile = c; dist = (float)distN; rotType = RotateType.Zero; }
                if (distE < dist) { nearestTile = c; dist = (float)distE; rotType = RotateType.Ninety;  }
                if (distS < dist) { nearestTile = c; dist = (float)distS; rotType = RotateType.OneEighty; }
                if (distW < dist) { nearestTile = c; dist = (float)distW; rotType = RotateType.TwoSeventy; }      
            }

            Coordinate vertex = Coordinate.Invalid;

            if (!nearestTile.IsInvalid)
                vertex = PickCorner(nearestTile, ray);

            return new GridPickData(nearestTile, vertex, rotType);
        }

        private float MinHeight(Coordinate overTile)
        {
            int xs = overTile.X / splitSize;
            int zs = overTile.Z / splitSize;

            int x = overTile.X % splitSize;
            int z = overTile.Z % splitSize;

            int tileNum = (z * splitSize + x) * 12;

            float lowest = float.MaxValue;

            for (int v = 0; v < 4; v++)
            {
                float height = vertices[xs, zs][tileNum + (v * 3)].Y;

                if (height < lowest)
                    lowest = height;
            }

            return lowest;
        }

        private float MaxHeight(Coordinate overTile)
        {
            int xs = overTile.X / splitSize;
            int zs = overTile.Z / splitSize;

            int x = overTile.X % splitSize;
            int z = overTile.Z % splitSize;

            int tileNum = (z * splitSize + x) * 12;

            float highest = float.NegativeInfinity;

            for (int v = 0; v < 4; v++)
            {
                float height = vertices[xs, zs][tileNum + (v * 3)].Y;

                if (height > highest)
                    highest = height;
            }

            return highest;
        }

        private Coordinate PickCorner(Coordinate tile, Ray ray)
        {
            int xs = tile.X / splitSize;
            int zs = tile.Z / splitSize;

            int x = tile.X % splitSize;
            int z = tile.Z % splitSize;

            int tileNum = (x + (z * splitSize)) * 12;
            int ne, se, sw, nw, cen;

            nw = tileNum;
            cen = tileNum + 1;
            ne = tileNum + 3;
            se = tileNum + 6;
            sw = tileNum + 9;

            Vector3 vectorSE = new Vector3(vertices[xs, zs][se].X, vertices[xs, zs][se].Y, vertices[xs, zs][se].Z);
            Vector3 vectorSW = new Vector3(vertices[xs, zs][sw].X, vertices[xs, zs][sw].Y, vertices[xs, zs][sw].Z);
            Vector3 vectorNW = new Vector3(vertices[xs, zs][nw].X, vertices[xs, zs][nw].Y, vertices[xs, zs][nw].Z);
            Vector3 vectorNE = new Vector3(vertices[xs, zs][ne].X, vertices[xs, zs][ne].Y, vertices[xs, zs][ne].Z);
            Vector3 vectorCentre = new Vector3(vertices[xs, zs][cen].X, vertices[xs, zs][cen].Y, vertices[xs, zs][cen].Z);

            List<Vector3> compassPoints = new List<Vector3>();

            compassPoints.Add(new Vector3(tile.X, AveHeight(vertices[xs, zs][nw], vertices[xs, zs][ne]), tile.Z - 0.5f)); // North
            compassPoints.Add(vectorNE); // NorthEast
            compassPoints.Add(new Vector3(tile.X + 0.5f, AveHeight(vertices[xs, zs][ne], vertices[xs, zs][se]), tile.Z)); //East
            compassPoints.Add(vectorSE); // SouthEast
            compassPoints.Add(new Vector3(tile.X, AveHeight(vertices[xs, zs][se], vertices[xs, zs][sw]), tile.Z + 0.5f)); // South
            compassPoints.Add(vectorSW); // SouthWest
            compassPoints.Add(new Vector3(tile.X - 0.5f, AveHeight(vertices[xs, zs][nw], vertices[xs, zs][sw]), tile.Z)); // West
            compassPoints.Add(vectorNW); // NorthWest
            compassPoints.Add(compassPoints[0]); // North again to complete the circle;

            int numTriangle = -1;


            float? dist = float.MaxValue;

            for (int i = 0; i < compassPoints.Count - 1; i++)
            {
                //bool check = Geometry.IntersectTri(compassPoints[i], vectorCentre, compassPoints[i + 1], localStart, localDirection, out intInf);
                Vector3 vectori = compassPoints[i];
                Vector3 vectoripo = compassPoints[i + 1];

                Intersection.RayIntersectsTriangle(ref ray, ref vectori, ref vectorCentre, ref vectoripo, out dist);

                if (dist != null)
                {
                    numTriangle = i;
                    break;
                }
            }

            if (numTriangle < 2) { return new Coordinate(tile.X + 1, tile.Z); }
            else if (numTriangle < 4) { return new Coordinate(tile.X + 1, tile.Z+1); }
            else if (numTriangle < 6) { return new Coordinate(tile.X, tile.Z+1); }
            else if (numTriangle < 8) { return new Coordinate(tile.X, tile.Z); }
            

            return Coordinate.Invalid;

        }

        private float AveHeight(TerrainVertex terrainVertex, TerrainVertex terrainVertex_2)
        {
            return (terrainVertex.Y + terrainVertex_2.Y) / 2;
        }


        #endregion
      
        #region Resource Handling
        public override void ReloadResources()
        {
            BuildVertexBuffer();
            FinaliseIndexBuffers();
        }

        private void BuildVertexBuffer()
        {
            updateNeeded = new bool[map.Width / splitSize, map.Length / splitSize];
            vertexBuffers = new VertexBuffer[map.Width / splitSize, map.Length / splitSize];
            int numberOfVerts = splitSize * splitSize * 12;
            int vbSize = numberOfVerts * vertexDeclaration.GetVertexStrideSize(0);

            for (int x = 0; x < map.Width / splitSize; x++)
            {
                for (int z = 0; z < map.Length / splitSize; z++)
                {
                    vertexBuffers[x, z] = new VertexBuffer(TTXNAGame.graphics.GraphicsDevice, vbSize, ResourceUsage.Dynamic, ResourceManagementMode.Manual);
                    vertexBuffers[x, z].SetData<TerrainVertex>(vertices[x, z]);
                }
            }
        } 
        #endregion

        #region Render
        public override void Render(TTCamera camera)
        {
            effect.Parameters["lightDir"].SetValue(Vector3.Normalize(new Vector3(0.5f, 1, 0.5f)));

            GraphicsDevice device = TTXNAGame.graphics.GraphicsDevice;

            device.RenderState.CullMode = CullMode.CullClockwiseFace;
            device.VertexDeclaration = vertexDeclaration;

            wvpParameter.SetValue(camera.View * camera.Projection);
            textureParameter.SetValue(grassTex);

            viewableLand.X = (((int)(camera.Position.X / splitSize) * splitSize) - (border * splitSize));
            viewableLand.Width = splitSize * (border * 2 + 1);
            viewableLand.Y = (((int)(camera.Position.Z / splitSize) * splitSize) - (border * splitSize));
            viewableLand.Height = splitSize * (border * 2 + 1);

            effect.Begin();
            effect.CurrentTechnique.Passes[0].Begin();

            //for (int zs = Math.Max(0, (int)(camera.Position.Z / splitSize) - border); zs < (int)(camera.Position.Z / splitSize) + border + 1; zs++)
            //{
            //    for (int xs = Math.Max(0, (int)(camera.Position.X / splitSize) - border); xs < (int)(camera.Position.X / splitSize) + border + 1; xs++)
            //    {
            //        if (xs >= 0 && zs >= 0 && xs < map.Width / splitSize && zs < map.Length / splitSize)
            //        {
            for(int zs = 0; zs < map.Length / splitSize; zs++)
            {
                for(int xs = 0; xs < map.Width / splitSize; xs++)
                {
                        device.Indices = gridIndexBuffer[xs, zs];
                        device.Vertices[0].SetSource(vertexBuffers[xs, zs], 0, vertexDeclaration.GetVertexStrideSize(0));
                        device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices[xs, zs].Length, 0, splitSize * splitSize * 4);
                    //}
                }
            }

            effect.CurrentTechnique.Passes[0].End();
            effect.End();
        }

        #endregion

        #region TTUpdateable Members

        public override void Update(GameTime elapsedTime)
        {
            List<CornerHeight> changes = map.ToUpdate;

            if (changes.Count > 0)
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                EnactChanges(changes);

                int sectorsUpdate = 0;

                for(int xs = 0; xs < map.Width / splitSize; xs++)
                    for (int zs = 0; zs < map.Length / splitSize; zs++)
                    {
                        if (updateNeeded[xs, zs])
                        {
                            vertexBuffers[xs, zs].SetData<TerrainVertex>(vertices[xs, zs]);
                            updateNeeded[xs, zs] = false;
                            sectorsUpdate++;
                        }
                    }
            }

            map.ClearChanges();
        }

        private void EnactChanges(List<CornerHeight> changes)
        {
            for (int i = 0; i < changes.Count; i++)
            {
                CornerHeight corner = changes[i];

                int xInSector = corner.X % splitSize;
                int zInSector = corner.Z % splitSize;
                int xSector = corner.X / splitSize;
                int zSector = corner.Z / splitSize;

                if (xInSector > 0)
                {
                    if (zInSector > 0)
                    {
                        UpdateVertex(xInSector, zInSector, xSector, zSector, corner.Height);
                    }
                    else
                    {
                        UpdateVertex(xInSector, 0, xSector, zSector, corner.Height);
                        UpdateVertex(xInSector, splitSize, xSector, zSector - 1, corner.Height);
                    }
                }
                else
                {
                    if (zInSector > 0)
                    {
                        UpdateVertex(0, zInSector, xSector, zSector, corner.Height);
                        UpdateVertex(splitSize, zInSector, xSector - 1, zSector, corner.Height);
                    }
                    else
                    {
                        UpdateVertex(0, 0, xSector, zSector, corner.Height); //Bottom Right
                        UpdateVertex(splitSize, 0, xSector - 1, zSector, corner.Height); //BottomLeft
                        UpdateVertex(splitSize, splitSize, xSector - 1, zSector - 1, corner.Height); //TopLeft
                        UpdateVertex(0, splitSize, xSector, zSector - 1, corner.Height);
                    }
                }
            }
        }

        private bool IsValidInSector(int x, int z)
        {
            return (x > -1 && z > -1 && x < splitSize && z < splitSize);
        }

        private void UpdateVertex(int xInSector, int zInSector, int xSector, int zSector, float height)
        {
            Coordinate topRightAbs = new Coordinate((xSector * splitSize) + xInSector, (zSector * splitSize) + zInSector - 1);
            Coordinate bottomRightAbs = new Coordinate((xSector * splitSize) + xInSector, (zSector * splitSize) + zInSector);
            Coordinate bottomLeftAbs = new Coordinate((xSector * splitSize) + xInSector - 1, (zSector * splitSize) + zInSector);
            Coordinate topLeftAbs = new Coordinate((xSector * splitSize) + xInSector - 1, (zSector * splitSize) + zInSector - 1);

            Coordinate topRight = new Coordinate(xInSector, zInSector - 1);
            Coordinate bottomRight = new Coordinate(xInSector, zInSector);
            Coordinate bottomLeft = new Coordinate(xInSector - 1, zInSector);
            Coordinate topLeft = new Coordinate(xInSector - 1, zInSector - 1);

            height /= 6;

            if (map.IsValid(topRightAbs) && IsValidInSector(xInSector, zInSector - 1))
            {
                int vertexNum = ((topRight.Z * splitSize + topRight.X) * 12);

                ModifyCorner(xSector, zSector, vertexNum + 8, height);
                ModifyCorner(xSector, zSector, vertexNum + 9, height);

                float middleHeight = (float)map.GetTileHeight(topRightAbs);

                RectifyMiddle(xSector, zSector, vertexNum, middleHeight / 6);
            }

            if (map.IsValid(bottomRightAbs) && IsValidInSector(xInSector, zInSector))
            {
                int vertexNum = (bottomRight.Z * splitSize + bottomRight.X) * 12;

                ModifyCorner(xSector, zSector, vertexNum + 0, height);
                ModifyCorner(xSector, zSector, vertexNum + 11, height);

                float middleHeight = (float)map.GetTileHeight(bottomRightAbs);

                RectifyMiddle(xSector, zSector, vertexNum, middleHeight / 6);
            }

            if (map.IsValid(bottomLeftAbs) && IsValidInSector(xInSector -1, zInSector))
            {
                int vertexNum = ((bottomLeft.Z * splitSize + bottomLeft.X) * 12);

                ModifyCorner(xSector, zSector, vertexNum + 2, height);
                ModifyCorner(xSector, zSector, vertexNum + 3, height);

                float middleHeight = (float)map.GetTileHeight(bottomLeftAbs);

                RectifyMiddle(xSector, zSector, vertexNum, middleHeight / 6);
            }

            if (map.IsValid(topLeftAbs) && IsValidInSector(xInSector - 1, zInSector - 1))
            {
                int vertexNum = ((topLeft.Z * splitSize + topLeft.X) * 12);

                ModifyCorner(xSector, zSector, vertexNum + 5, height);
                ModifyCorner(xSector, zSector, vertexNum + 6, height);

                float middleHeight = (float)map.GetTileHeight(topLeftAbs);

                RectifyMiddle(xSector, zSector, vertexNum, middleHeight / 6);
            }
        }

        private void RectifyMiddle(int xSector, int zSector, int baseVertex, float height)
        {
            ModifyCorner(xSector, zSector, baseVertex + 1, height);
            ModifyCorner(xSector, zSector, baseVertex + 4, height);
            ModifyCorner(xSector, zSector, baseVertex + 7, height);
            ModifyCorner(xSector, zSector, baseVertex + 10, height);
        }

        private void ModifyCorner(int xSector, int zSector, int vertexNum, float height)
        {
            vertices[xSector, zSector][vertexNum].Y = height;

            int triNum = (vertexNum / 3) * 3;

            Vector3 u = vertices[xSector, zSector][triNum + 1].Position - vertices[xSector, zSector][triNum].Position;
            Vector3 v = vertices[xSector, zSector][triNum + 2].Position - vertices[xSector, zSector][triNum].Position;

            Vector3 cross = Vector3.Cross(u, v);
            cross.Normalize();

            vertices[xSector, zSector][triNum].Normal = cross;
            vertices[xSector, zSector][triNum + 1].Normal = cross;
            vertices[xSector, zSector][triNum + 2].Normal = cross;

            updateNeeded[xSector, zSector] = true;
        }

        #endregion
    }

    public struct TerrainVertex
    {
        private Vector3 position;
        private Vector3 normal;
        private Vector2 texCoord;

        public TerrainVertex(Vector3 position, Vector3 normal, Vector2 textureCoordinate)
        {
            this.position = position;
            this.normal = normal;
            this.texCoord = textureCoordinate;
        }

        public float X
        {
            get { return position.X; }
            set { position.X = value; }
        }

        public float Y
        {
            get { return position.Y; }
            set { position.Y = value; }
        }

        public float Z
        {
            get { return position.Z; }
            set { position.Z = value; }
        }

        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }

        public Vector3 Normal
        {
            get { return normal; }
            set { normal = value; }
        }
    }

    public struct GridPickData
    {
        private Coordinate coordinate;
        private Coordinate vertex;
        private RotateType side;

        public GridPickData(Coordinate coordinate, Coordinate vertex, RotateType side)
        {
            this.coordinate = coordinate;
            this.vertex = vertex;
            this.side = side;
        }

        public Coordinate Coord
        {
            get { return coordinate; }
        }

        public Coordinate CornerVertex
        {
            get { return vertex; }
        }

        public RotateType TileSide
        {
            get { return side; }
        }

    }

    public enum HighlightType { NoHighlight, Half, Full }

}